package com.unittec.drainage.provider.log.controller;

import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.CoreConstants;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.unittec.drainage.provider.log.entity.SysLogEntity;
import com.unittec.drainage.provider.log.enums.BizErrorCode102;
import com.unittec.drainage.provider.common.BeanValidator;
import com.unittec.drainage.provider.log.service.LogService;
import com.unittec.drainage.provider.log.service.MQLogProducerService;
import com.unittec.drainage.provider.scope.GlobalScope;
import com.unittec.drainage.provider.scope.ThreadLocalScope;
import com.unittec.drainage.provider.service.RootCommonUtils;
import com.unittec.drainage.sdk.auth.domain.SysUserRes;
import com.unittec.drainage.sdk.log.enums.LogOperateEnum;
import com.unittec.drainage.sdk.log.enums.LogStatusEnum;
import com.unittec.drainage.sdk.log.request.*;
import com.unittec.drainage.sdk.log.response.*;
import com.unittec.drainage.sdk.root.domain.GatewayRequest;
import com.unittec.drainage.sdk.root.domain.Result;
import com.unittec.drainage.sdk.root.enums.BizCode;
import com.unittec.drainage.sdk.root.enums.BizCodeEnum;
import com.unittec.drainage.sdk.root.enums.LogLevelE;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 开放平台的统一日志服务接口，可以对每个服务配置日志级别，配置后实时通知对应的服务 所有服务的日志都统一在日志服务中处理，便于日志的管理和排查问题
 */
@RestController
@Api(tags = "日志管理")
public class LogController {

    private static final Logger logger = LoggerFactory.getLogger(LogController.class);

    private static final DateTimeFormatter DATEFORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    @Resource
    private LogService logService;

    @Resource
    private MQLogProducerService msgProducer;

    @Resource
    private RootCommonUtils rootCommonUtils;

    private void checkRequestId(GatewayRequest<?> req) {
        if (StringUtils.isBlank(req.getRequestId())) {
            req.setRequestId(ThreadLocalScope.getAuthData().getRequestId());
        }
    }

    @RequestMapping("/102011")
    @ApiOperation(value = "应用创建日志", notes = "应用创建日志")
    public Result<Res102011> createLog(@RequestBody GatewayRequest<Req102011> req) {
        System.out.println("创建日志:========" + JSON.toJSONString(req));
        long startLogTime = System.currentTimeMillis();
        Res102011 res102011 = new Res102011();
        try {
            log(req, "createLog begin", null);
            checkRequestId(req);
            Req102011 req102011 = new Req102011();
            Req102011 content = req.getContent();
            BeanValidator.validate(content);
            req102011.setAppId(content.getAppId());
            req102011.setLogName(content.getLogName());
            String logName = logService.getLogName(content.getAppId());
            if (StringUtils.isNotBlank(content.getLogName()) && content.getLogName().equals(logName)) {
                res102011.setABoolean(false);
                res102011.setMsg(BizErrorCode102.APPLOG_ALREADY_CREATED.getMessage());
                assOperateLog("102011", JSON.toJSONString(req.getContent()), "应用创建日志", BizCode.SUCCESS.getMessage(),
                        LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(), startLogTime);
                return Result.error(req, res102011, BizErrorCode102.APPLOG_ALREADY_CREATED);
            }
            if (!logService.createLog(req102011)) {
                res102011.setABoolean(false);
                res102011.setMsg(BizErrorCode102.APPLOG_NAME_EXIST.getMessage());
                assOperateLog("102011", JSON.toJSONString(req.getContent()), "应用创建日志", BizCode.ERROR.getMessage(),
                        LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(), startLogTime);
                return Result.error(req, BizErrorCode102.APPLOG_NAME_EXIST);
            }
            res102011.setABoolean(true);
            return Result.success(req, res102011);
        } catch (Exception e) {
            // 添加错误信息
            assOperateLog("102011", JSON.toJSONString(req.getContent()), "应用创建日志", buildErrmsg(e),
                    LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(), startLogTime);
            return Result.error(req, e.getLocalizedMessage());
        }

    }

    // 配置应用的业务日志级别后，需要通知对应的服务，刷新对应的日志级别，便于服务的实现代码的日志级别刷新，避免不必要的日志拼接操作
    // 服务实现代码记录日志的规范：先判断日志级别，再记录日志(有字符串的拼接操作，会耗性能)
    @RequestMapping("/102012")
    @ApiOperation(value = "配置应用的指定服务日志级别", notes = "配置应用的指定服务日志级别")
    public Result<Res102012> setLogLevel(@RequestBody GatewayRequest<Req102012> req) {
        long startLogTime = System.currentTimeMillis();
        Res102012 res102012 = new Res102012();
        try {
            log(req, "setLogLevel begin", null);
            checkRequestId(req);
            Req102012 content = req.getContent();
            BeanValidator.validate(content);
            if (null != logService.getLogName(content.getAppId())) {
                String[] serviceIds = content.getServiceId();
                for (String serviceId : serviceIds) {
                    Req102011 req102011 = new Req102011();
                    req102011.setAppId(content.getAppId());
                    req102011.setServiceId(serviceId);
                    req102011.setLogLevel(content.getLogLevel());
                    logService.setLogLevel(req102011);
                }
                res102012.setABoolean(true);
                assOperateLog("102012", JSON.toJSONString(req.getContent()), "配置应用的指定服务日志级别成功",
                        BizCode.SUCCESS.getMessage(), LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(),
                        startLogTime);
                return Result.success(req, res102012);
            }
            res102012.setABoolean(false);
            assOperateLog("102012", JSON.toJSONString(req.getContent()), "配置应用的指定服务日志级别失败",
                    BizCode.SUCCESS.getMessage(), LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(),
                    startLogTime);
            return Result.error(req, BizErrorCode102.APPLOG_NO_CREATED);
        } catch (Exception e) {
            res102012.setABoolean(false);
            assOperateLog("102012", JSON.toJSONString(req.getContent()), "配置应用的指定服务日志级别失败", buildErrmsg(e),
                    LogOperateEnum.LOG_LIST.getCode(), LogStatusEnum.ERROR.getCode(), startLogTime);
            return Result.error(req, e.getLocalizedMessage());
        }
    }

    // 后续集成logback，按照实际服务日志记录到文件中
    // 采用一个应用记录一个日志文件
    @RequestMapping(value = "/102013", method = RequestMethod.POST, produces = "application/json",
            consumes = "application/json")
    @ApiOperation(value = "应用的指定服务记录日志", notes = "应用的指定服务记录日志")
    public Result<java.lang.String> log(@RequestBody GatewayRequest<Req102013> req) {
        checkRequestId(req);
        Req102013 log = req.getContent();
        if (null == log.getAppId()) {
            log.setAppId(req.getAppId());
        }
        try {
            LogLevelE logLevel = logService.getLogLevel(log);
            if (null != logLevel) {
                if (log.getLogLevel().getLevel() >= logLevel.getLevel()) {
                    logService.log(log);
                }
                return Result.success(req, "成功");
            }
            return Result.error(req, BizErrorCode102.APPLOG_NO_CREATED);
        } catch (Exception e) {
            return Result.error(req, e.toString());
        }
    }

    /**
     * @param req
     * @return
     */
    @RequestMapping(value = "/102014", method = RequestMethod.POST, produces = "application/json",
            consumes = "application/json")
    @ApiOperation(value = "获取应用的指定服务日志级别", notes = "获取应用的指定服务日志级别")
    public Result<Res102014> getLogInfo(@RequestBody GatewayRequest<java.lang.String> req) {
        try {
            checkRequestId(req);
            if (null != logService.getLogName(req.getAppId())) {
                Req102011 req102011 = new Req102011();
                req102011.setAppId(req.getAppId());
                req102011.setServiceId(req.getContent());
                Res102014 res = logService.getLogInfo(req102011);
                return Result.success(req, res);
            }
            return Result.error(req, BizErrorCode102.APPLOG_NO_CREATED);
        } catch (Exception e) {
            return Result.error(req, e.toString());
        }
    }

    // 业务服务重启后，请求这接口获取服务对应的各个应用的日志级别
    @RequestMapping("/102015")
    @ApiOperation(value = "获取指定服务的所有应用记录级别", notes = "获取指定服务的所有应用日志级别")
    public Result<List<Res102015>> getLogLevels(@RequestBody GatewayRequest<java.lang.String> req) {
        try {
            checkRequestId(req);
            List<Res102015> list = logService.getLogLevels(req.getContent());
            return Result.success(req, list);
        } catch (Exception e) {
            logger.error("getLogLevels error", e);
            return Result.error(req, e.toString());
        }
    }

    // 记录系统功能日志，如定时任务，消息队列，系统初始化
    @RequestMapping(value = "/102016", method = RequestMethod.POST, produces = "application/json",
            consumes = "application/json")
    @ApiOperation(value = "记录系统日志", notes = "记录系统日志")
    public Result<java.lang.String> syslog(@RequestBody GatewayRequest<Req102014> req) {
        try {
            checkRequestId(req);
            logService.log(req.getContent());
            return Result.success(req, "成功");
        } catch (Exception e) {
            return Result.error(req, e.toString());
        }
    }

    /**
     * 系统AOP日志
     *
     * @param req
     * @return
     */
    @RequestMapping(value = "/102023", method = RequestMethod.POST, produces = "application/json",
            consumes = "application/json")
    @ApiOperation(value = "记录系统日志", notes = "记录系统日志")
    public Result<java.lang.String> sysAopMethodlog(@RequestBody GatewayRequest<Req102014> req) {
        try {
            checkRequestId(req);
            logService.aopMethodlog(req.getContent());
            return Result.success(req, "成功");
        } catch (Exception e) {
            return Result.error(req, e.toString());
        }
    }

    private String buildErrmsg(Throwable throwable) {
        StringBuilder builder = new StringBuilder();
        ThrowableProxy throwableProxy = new ThrowableProxy(throwable);
        for (StackTraceElementProxy step : throwableProxy.getStackTraceElementProxyArray()) {
            String string = step.toString();
            builder.append(CoreConstants.TAB).append(string);
            ThrowableProxyUtil.subjoinPackagingData(builder, step);
            builder.append(CoreConstants.LINE_SEPARATOR);
        }
        return builder.toString();
    }

    private void log(GatewayRequest<?> req, String log, String errmsg) {
        Req102013 content = new Req102013();
        content.setReqTime(LocalDateTime.now().format(DATEFORMAT));
        content.setLogLevel(GlobalScope.getLogLevel(req.getAppId()));
        content.setServiceId(GlobalScope.getServiceName());
        content.setAppId(req.getAppId());
        content.setRequestId(req.getRequestId());
        content.setMethod(req.getMethod());
        // 如果是文件上传类的消息体会很大，对于超过2K的消息体，只记录轨迹，不记录日志详细内容
        if (log.length() < 2048) {
            content.setLogContent(log);
        }
        content.setErrMsg(errmsg);
        logService.log(content);
    }

    /**
     * 拼装添加操作日志
     *
     * @param reqUrl
     * @param reqParam
     * @param logTitle
     * @param logContent
     * @param operateType
     * @param operateStatus
     * @param startTime
     */
    private GatewayRequest<Req102017> assOperateLog(String reqUrl, String reqParam, String logTitle, String logContent,
                                                    String operateType, String operateStatus, long startTime) {
        long endTime = System.currentTimeMillis();
        GatewayRequest<Req102017> gatewayRequest = new GatewayRequest<>();
        Req102017 content = new Req102017();
        try {
            content.setLogType(2);
            content.setCostTime(endTime - startTime);
            content.setOperateType(operateType);
            content.setOperateStatus(operateStatus);
            content.setRequestParam(reqParam);
            content.setLogTitle(logTitle);
            content.setLogContent(logContent);
            content.setMethod(reqUrl);
            gatewayRequest.setContent(content);
            return gatewayRequest;
        } catch (Exception e) {
            // 捕获到异常后，在本地记录日志，确保日志信息不会丢失
            logger.error("log content=【{}】 errmsg=【{}】", JSON.toJSONString(gatewayRequest), e.toString(), e);
            return gatewayRequest;
        }
    }

    @RequestMapping("/102017")
    @ApiOperation(value = "新增系统操作日志", notes = "新增系统操作日志")
    public Result<String> addOperateLog(@RequestBody GatewayRequest<Req102017> request) {
        log(request, "addOperateLog begin", null);
        try {
            BeanValidator.validate(request.getContent());
            request.getContent().setAppId(ThreadLocalScope.getAuthData().getAppId());
            request.getContent().setTenantId(ThreadLocalScope.getAuthData().getTenantId());
            request.getContent().setIp(ThreadLocalScope.getAuthData().getIp());
            request.getContent().setAppCode(ThreadLocalScope.getAuthData().getAppCode());
            request.getContent().setRequestId(ThreadLocalScope.getAuthData().getRequestId());
            request.getContent().setToken(ThreadLocalScope.getAuthData().getToken());
            request.getContent().setUser(ThreadLocalScope.getAuthData().getUser());


            msgProducer.createLog(request);
        } catch (Exception e) {
            log(request, "createLog", buildErrmsg(e));
            return Result.error(request, e.toString());
        }
        log(request, "addOperateLog end", null);
        return Result.success(request, "成功");
    }

    @RequestMapping(value = "/102018", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "日志分页列表", notes = "日志分页列表")
    public Result<Res102018> logListForPage(@RequestBody GatewayRequest<Req102018> req) {
        log(req, "logListForPage{}  ", "begin");
        BizCodeEnum bizCodeEnum = BizCode.ERROR;
        Res102018 res102018 = null;
        try {
            IPage<Map<String, Object>> logList = logService.logListForPage(req.getContent());

            List<String> ids = new ArrayList<>();
            if (logList.getRecords() != null && logList.getRecords().size() > 0) {

                for (Map<String, Object> map : logList.getRecords()) {
                    String userId = map.get("user_id") == null ? "" : String.valueOf(map.get("user_id"));
                    if (StringUtils.isNotBlank(userId)) {
                        ids.add(userId);
                    }
                }
            }

            if (ids.size() > 0) {
                Map<String, SysUserRes> userResMap = rootCommonUtils.getUserByIds(ids);
                for (Map<String, Object> map : logList.getRecords()) {
                    String userId = map.get("user_id") == null ? "" : String.valueOf(map.get("user_id"));
                    if (StringUtils.isNotBlank(userId)) {
                        String realName = userResMap.get(userId) == null ? "" : userResMap.get(userId).getRealname();
                        if (StringUtils.isNotBlank(realName)) {
                            map.put("rela_name", realName);
                        }

                    }
                }
            }


            res102018 = new Res102018();
            res102018.setPage(logList);
            bizCodeEnum = BizCode.SUCCESS;
        } catch (Exception e) {
            log(req, e.toString(), e.getLocalizedMessage());
        }
        log(req, "logListForPage {}  ", "end");

        if (bizCodeEnum == BizCode.SUCCESS) {
            return Result.success(req, res102018);
        } else {
            return Result.error(req, bizCodeEnum);
        }
    }

    @RequestMapping(value = "/102019", method = RequestMethod.POST)
    @ResponseBody
    @ApiOperation(value = "操作日志详情", notes = "操作日志详情")
    public Result<Res102019> queryLog(@RequestBody GatewayRequest<Req102019> req) {
        BizCodeEnum bizCodeEnum = BizCode.ERROR;
        log(req, "queryLog {}  ", "begin");
        Res102019 res102019 = new Res102019();
        try {
            BeanValidator.validate(req.getContent());
            SysLogEntity sysLogEntity = logService.getById(req.getContent().getId());
            if (ObjectUtils.isNotNull(sysLogEntity)) {
                BeanUtils.copyProperties(sysLogEntity, res102019);
                bizCodeEnum = BizCode.SUCCESS;
            }
        } catch (Exception e) {
            log(req, e.toString(), e.getLocalizedMessage());
            return Result.error(req, e.toString());
        }
        log(req, "queryLog {}  ", "end");
        if (bizCodeEnum == BizCode.SUCCESS) {
            return Result.success(req, res102019);
        } else {
            return Result.error(req, bizCodeEnum);
        }
    }

    /**
     * 根据应用appID获取各个服务的日志级别
     *
     * @param req
     * @return
     */
    @RequestMapping("/102020")
    @ApiOperation(value = "根据应用appID获取各个服务的日志级别", notes = "根据应用appID获取各个服务的日志级别")
    public Result<List<Res102020>> getLogLevelsByAppId(@RequestBody GatewayRequest<Req102020> req) {
        try {
            log(req, "getLogLevelsByAppId {}  ", "begin");
            checkRequestId(req);
            Req102020 content = req.getContent();
            BeanValidator.validate(content);
            List<Res102020> list = logService.getLogLevelsByAppId(content.getAppId());
            log(req, "getLogLevelsByAppId {}  ", "end");
            return Result.success(req, list);
        } catch (Exception e) {
            logger.error("getLogLevels error", e);
            return Result.error(req, e.toString());
        }
    }

    /**
     * 根据应用appID获取日志名称
     *
     * @param req
     * @return
     */
    @RequestMapping("/102021")
    @ApiOperation(value = "根据应用appID获取日志名称", notes = "根据应用appID获取日志名称")
    public Result<Res102021> getLogNameByAppId(@RequestBody GatewayRequest<Req102021> req) {
        try {
            log(req, "getLogNameByAppId {}  ", "begin");
            checkRequestId(req);
            Req102021 content = req.getContent();
            BeanValidator.validate(content);
            String logName = logService.getLogName(content.getAppId());
            Res102021 res102021 = new Res102021();
            res102021.setAppId(content.getAppId());
            res102021.setLogName(logName);
            log(req, "getLogNameByAppId {}  ", "end");
            return Result.success(req, res102021);
        } catch (Exception e) {
            logger.error("getLogNameByAppId error", e);
            return Result.error(req, e.toString());
        }
    }

    /**
     * 根据应用appID获取日志名称
     *
     * @return
     */
    @ApiOperation(value = "上传app日志文件", notes = "上传app日志文件")
    @RequestMapping(value = "/102022", method = RequestMethod.POST)
    public Res102022 appLogUpload(@RequestPart(value = "file") MultipartFile file) {
        Res102022 res102022 = new Res102022();
        try {
            if (file == null) {
                res102022.setObj(false);
                return res102022;
            }
            String originalFilename = file.getOriginalFilename();
            if (StringUtils.isNotBlank(originalFilename)) {
                res102022.setObj(false);
                return res102022;
            }
            originalFilename = originalFilename.substring(0, originalFilename.lastIndexOf("."));
            logService.uploadAppLog(originalFilename, file.getBytes());
            res102022.setObj(true);
            return res102022;
        } catch (Exception e) {
            logger.error("appLogUpload error", e);
            res102022.setObj(false);
            return res102022;
        }
    }

}
